package com.lwj.file.service.impl;


import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.poi.excel.BigExcelWriter;
import cn.hutool.poi.excel.ExcelUtil;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.lwj.file.entity.Order;
import com.lwj.file.entity.OrderDTO;
import com.lwj.file.mapper.OrderDao;
import com.lwj.file.mapper.OrderMapper;
import com.lwj.file.util.PoiSXSSFWorkbookExcel;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderDTO> {


    @Resource
    private OrderMapper orderMapper;

    @Resource(name = "taskExecutor")
    private ThreadPoolExecutor threadPoolExecutor;

    @Resource
    private OrderDao orderDao;


    @Async
    public void export(HttpServletResponse response, Integer pageSize, Integer limit) throws UnsupportedEncodingException {
        Date dateBefore = new Date();
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("UTF-8");// 防止下载的文件名字乱码
        // 文件以附件形式下载
        response.setHeader("Content-disposition",
                "attachment;filename=down_" + URLEncoder.encode(System.currentTimeMillis() + ".xlsx", "utf-8"));
        try {
            long start = System.currentTimeMillis();
            String sheetName = "业务清单";
            String[] title = new String[]{
                    "订单号",
                    "修改人",
                    "创建人"
            };
            List<List<String>> data = new ArrayList<>();
            List<Order> userEntityList = selectAll(pageSize, limit);
            for (Order userEntity : userEntityList) {
                List<String> rowData = new ArrayList<>();
                rowData.add(userEntity.getOrderId()); //订单号
                rowData.add(userEntity.getEditor()); //修改人
                rowData.add(userEntity.getCreator()); //创建人
                data.add(rowData);
            }
            BigExcelWriter writer = ExcelUtil.getBigWriter("xxx.xlsx");
            int sheetNum = 0;// 记录额外创建的sheet数
            writer.setSheet(sheetName + sheetNum);

            SXSSFWorkbook wb = PoiSXSSFWorkbookExcel.getSxssfwbManySameSheets(sheetName, title, data);
            System.out.println("查询数据条数:" + userEntityList.size() + " 数据库查询数据总耗时：  " + (System.currentTimeMillis() - start) + "ms");
            ServletOutputStream output = response.getOutputStream();
            wb.write(output);
            output.flush();
            Date dateAfter = new Date();
            System.out.println("共" + userEntityList.size() + "条数据,导出列表共执行" + (dateAfter.getTime() - dateBefore.getTime()) + "ms");
            output.close();
            //todo 上传oss 发送通知
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public List<Order> selectAll(Integer pageSize, Integer limit) {
        long start = System.currentTimeMillis();
        List<MyCallableTsk> myCallableTsks = new ArrayList<>();
        // 计算出多少页，即循环次数
        int count = pageSize / limit + (pageSize % limit > 0 ? 1 : 0);

        CountDownLatch cd = new CountDownLatch(count);
        for (int i = 1; i <= count; i++) {
            myCallableTsks.add(new MyCallableTsk(i, limit, cd));
        }
        List<Order> OrderList = new ArrayList<>();
        try {
            List<Future<List<Order>>> futures = threadPoolExecutor.invokeAll(myCallableTsks);
            for (Future<List<Order>> future : futures) {
                OrderList.addAll(future.get());
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("主线程：" + Thread.currentThread().getName() + " , 导出指定数据成功 , 共导出数据：" + OrderList.size() + " ,查询数据任务执行完毕共消耗时 ：" + (end - start) + "ms");
        return OrderList;
    }

    @Async
    public void export() throws IOException {
        Date dateBefore = new Date();
        String fileName = URLEncoder.encode(String.format("%s-(%s).xlsx", "订单数据", UUID.randomUUID()),
                StandardCharsets.UTF_8.toString());
        BufferedOutputStream outputStream = FileUtil.getOutputStream(FileUtil.file("/Users/luweijie/Documents/github/MultithreadedExportExcelData/sql/" + fileName));

        ExcelWriter writer = new ExcelWriterBuilder()
                .autoCloseStream(true)
                .excelType(ExcelTypeEnum.XLSX)
                .file(outputStream)
                .head(OrderDTO.class)
                .build();
        cn.hutool.poi.excel.ExcelWriter bigWriter = ExcelUtil.getBigWriter(10000);

        WriteSheet writeSheet = new WriteSheet();
        writeSheet.setSheetName("target");
        long lastBatchMaxId = 0L;
        int limit = 10000;
        for (; ; ) {
            List<OrderDTO> list = queryByScrollingPagination(lastBatchMaxId, limit);
            //可以添加导出条数限制
            if (list.isEmpty()) {
                bigWriter.flush(outputStream);
                writer.finish();
                Date dateAfter = new Date();
                System.out.println("导出列表共执行" + (dateAfter.getTime() - dateBefore.getTime()) + "ms");
                //todo 上传oss 发通知
                break;
            } else {
                lastBatchMaxId = list.stream().map(OrderDTO::getId).max(Long::compareTo).orElse(Long.MAX_VALUE);
                bigWriter.write(list);
                writer.write(list, writeSheet);

            }
        }
    }

    public List<OrderDTO> queryByScrollingPagination(long lastBatchMaxId,
                                                     int limit) {
        return orderDao.queryByScrollingPagination(lastBatchMaxId, limit).stream().map(order -> {
            OrderDTO dto = new OrderDTO();
            dto.setId(order.getId());
            dto.setAmount(order.getAmount());
            dto.setOrderId(order.getOrderId());
            dto.setCreator(order.getCreator());
            return dto;
        }).collect(Collectors.toList());
    }


    class MyCallableTsk implements Callable<List<Order>> {
        private OrderMapper orderMapper;
        private CountDownLatch cd;

        private Integer pageNum;
        private Integer pageSize;

        public MyCallableTsk(Integer pageNum, Integer pageSize, CountDownLatch cd) {
            this.pageNum = pageNum;
            this.pageSize = pageSize;
            this.cd = cd;
        }

        @Override
        public List<Order> call() throws Exception {
            System.out.println("线程：" + Thread.currentThread().getName() + " , 开始读取数据------");
            long start = System.currentTimeMillis();
            orderMapper = (OrderMapper) SpringUtil.getBean("orderMapper");
            PageHelper.startPage(pageNum, pageSize);
            List<Order> OrderList = OrderServiceImpl.this.orderMapper.selectAll();
            System.out.println("线程：" + Thread.currentThread().getName() + " , 读取数据  " + OrderList.size() + ",页数:" + pageNum + "耗时 ：" + (System.currentTimeMillis() - start) + "ms");
            cd.countDown();
            System.out.println("剩余任务数  ===========================> " + cd.getCount());
            return OrderList;
        }
    }
}
